﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Xml.Linq;
using Microsoft.Win32;


static class Program
{
  [DebuggerDisplay("{Id}")]
  class Feature
  {
    public Guid Guid;
    public string Id, Title, Description;
    public Feature Parent;
    public List<Component> Components = new List<Component>();
  }

  [DebuggerDisplay("{FullPath}")]
  class Directory
  {
    public string Id;
    public string Name;
    public Directory Parent;

    public string FullPath
    {
      get
      {
        if (Parent == null)
          return Name;

        var root = Parent.FullPath;

        if (root == "SourceDir")
          switch (Name)
          {
            case "Program Files":
              return Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles);

            case "Program Files (x86)":
              return Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86);

            case "CommonAppData":
              return Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData);

            case "Win":
              return Environment.GetFolderPath(Environment.SpecialFolder.Windows);
          }
        //else
        //  switch (Name)
        //  {
        //    case "URTInstallPath_x86_GAC20":
        //      return Environment.GetFolderPath(Environment.SpecialFolder.);
        //  }

        return root + @"\" + Name;
      }
    }
  }

  [DebuggerDisplay("{Id}")]
  class Component
  {
    public string Id;
    public Guid Guid;
    public Directory Directory;
    public List<Feature> Features = new List<Feature>();
    public List<File> Files = new List<File>();
    public List<RegistryEntry> RegistryEntries = new List<RegistryEntry>();
  }

  [DebuggerDisplay("{FullPath}")]
  class File
  {
    public string Id, Name;
    public Component Component;
    public string FullPath { get { return Component.Directory.FullPath + @"\" + Name; } }

    public bool Exists()
    {
      return System.IO.File.Exists(FullPath);
    }

    public void Delete()
    {
      System.IO.File.Delete(FullPath);
    }
  }

  [DebuggerDisplay("{FullPath}")]
  class RegistryEntry
  {
    public Component Component;
    public string Id, Key, Name, Value;
    public int Root;

    public bool Exists()
    {
      var root = GetRootKey();

      var key = root.OpenSubKey(Key);
      if (key == null)
        return false;

      using (key)
        return key.GetValue(Name) != null;
    }

    RegistryKey GetRootKey()
    {
      var root = Registry.CurrentUser;

      switch (Root)
      {
        case 0:
          root = Registry.ClassesRoot;
          break;

        case 2:
          root = Registry.LocalMachine;
          break;

        case 3:
          root = Registry.Users;
          break;
      }
      return root;
    }

    public string FullPath
    {
      get
      {
        var result = Key;

        switch (Root)
        {
          case 0:
            return @"HKEY_CLASSES_ROOT\" + result;

          case 2:
            return @"HKEY_LOCAL_MACHINE\" + result;

          case 3:
            return @"HKEY_USERS\" + result;

          default:
            return @"HKEY_CURRENT_USER\" + result;
        }
      }
    }

    public void Delete()
    {
      if (string.IsNullOrEmpty(Name))
        GetRootKey().DeleteSubKeyTree(Key, false);
      else
      {
        var key = GetRootKey().OpenSubKey(Key, true);
        if (key == null)
          return; // deleted already

        using (key)
          key.DeleteValue(Name);
      }
    }
  }

  static void Main(string[] args)
  {
    var msi = XElement.Load("vs.xml");

    var dirs = ReadDirectories(msi);

    var features = ReadFeatures(msi);

    var components = ReadComponents(msi, features, dirs);

    var files = ReadFiles(msi, components);

    var rentries = ReadRegistryEntries(msi, components);

    var te = features["TeamExplorer_enu"];

    var toDelete = (from c in te.Components
                    where c.Features.Count == 1
                    select c).ToList();

    var toInspect = (from c in te.Components
                     where c.Features.Count > 1
                     select c).ToList();

    var toDelete2 = (from c in toInspect
                     where c.Files.Any(i => i.Name.IndexOf("TeamFoundation", StringComparison.OrdinalIgnoreCase) >= 0)
                     select c).ToList();

    toDelete.AddRange(toDelete2);

    var allEntries = (from c in toDelete from r in c.RegistryEntries select r).ToList();

    var badEntries = (from r in allEntries where !r.Exists() select r).ToList();

    System.IO.File.WriteAllLines("entries.txt", badEntries.Select(i => i.FullPath));

    var allFiles = (from c in toDelete from f in c.Files select f).ToList();

    var badFiles = (from f in allFiles where !f.Exists() select f).ToList();

    System.IO.File.WriteAllLines("files.txt", badFiles.Select(i => i.FullPath));

    foreach (var f in allFiles.Except(badFiles))
      f.Delete();

    foreach (var e in allEntries.Except(badEntries))
      e.Delete();
  }

  static Dictionary<string, RegistryEntry> ReadRegistryEntries(XElement msi, Dictionary<string, Component> components)
  {
    var result = (from t in msi.Elements("table")
                  let name = t.Attribute("name")
                  where name != null && name.Value == "Registry"
                  from r in t.Elements("row")
                  let elements = r.Elements("td").GetEnumerator()
                  select new RegistryEntry
                  {
                    Id = GetElementValue(elements),
                    Root = Convert.ToInt32(GetElementValue(elements)),
                    Key = FormatVar(GetElementValue(elements)),
                    Name = FormatVar(GetElementValue(elements)),
                    Value = GetElementValue(elements),
                    Component = components[GetElementValue(elements)]
                  }).ToDictionary(i => i.Id);

    foreach (var r in result)
      r.Value.Component.RegistryEntries.Add(r.Value);

    return result;
  }

  static string FormatVar(string p)
  {
    if (p == null)
      return p;

    return p.Replace("[VSAssemblyVersion]", "10.0.0.0");
  }

  static Dictionary<string, Directory> ReadDirectories(XElement msi)
  {
    var dirs = (from t in msi.Elements("table")
                let name = t.Attribute("name")
                where name != null && name.Value == "Directory"
                from r in t.Elements("row")
                let elements = r.Elements("td").GetEnumerator()
                select new
                {
                  Id = GetElementValue(elements),
                  Parent = GetElementValue(elements),
                  DefaultDir = GetElementValue(elements)
                }).ToList();

    var result = (from t in dirs
                  select new Directory
                  {
                    Id = t.Id,
                    Name = t.DefaultDir.LongName()
                  }).ToDictionary(i => i.Id);

    foreach (var d in dirs)
      if (!string.IsNullOrEmpty(d.Parent))
        result[d.Id].Parent = result[d.Parent];

    return result;
  }

  static Dictionary<string, File> ReadFiles(XElement msi, Dictionary<string, Component> components)
  {
    var result = (from t in msi.Elements("table")
                  let name = t.Attribute("name")
                  where name != null && name.Value == "File"
                  from r in t.Elements("row")
                  let elements = r.Elements("td").GetEnumerator()
                  select new File
                  {
                    Id = GetElementValue(elements),
                    Component = components[GetElementValue(elements)],
                    Name = GetElementValue(elements).LongName()
                  }).ToDictionary(i => i.Id);

    foreach (var f in result)
      f.Value.Component.Files.Add(f.Value);

    return result;
  }

  static Dictionary<string, Component> ReadComponents(XElement msi, Dictionary<string, Feature> features, Dictionary<string, Directory> dirs)
  {
    var result = (from t in msi.Elements("table")
                  let name = t.Attribute("name")
                  where name != null && name.Value == "Component"
                  from r in t.Elements("row")
                  let elements = r.Elements("td").GetEnumerator()
                  select new Component
                  {
                    Id = GetElementValue(elements),
                    Guid = new Guid(GetElementValue(elements)),
                    Directory = dirs[GetElementValue(elements)]
                  }).ToDictionary(i => i.Id);

    var c2f = from t in msi.Elements("table")
              let name = t.Attribute("name")
              where name != null && name.Value == "FeatureComponents"
              from r in t.Elements("row")
              let elements = r.Elements("td").GetEnumerator()
              select new
              {
                Feature = features[GetElementValue(elements)],
                Component = result[GetElementValue(elements)]
              };

    foreach (var data in c2f)
    {
      data.Feature.Components.Add(data.Component);
      data.Component.Features.Add(data.Feature);
    }

    return result;
  }

  static Dictionary<string, Feature> ReadFeatures(XElement msi)
  {
    var features = (from t in msi.Elements("table")
                    let name = t.Attribute("name")
                    where name != null && name.Value == "Feature"
                    from r in t.Elements("row")
                    let elements = r.Elements("td").GetEnumerator()
                    select new
                    {
                      Id = GetElementValue(elements),
                      ParentId = GetElementValue(elements),
                      Title = GetElementValue(elements),
                      Description = GetElementValue(elements)
                    }).ToList();

    var result = (from x in features
                  select new Feature
                  {
                    Id = x.Id,
                    Title = x.Title,
                    Description = x.Description
                  }).ToDictionary(i => i.Id);

    foreach (var f in features)
      if (!string.IsNullOrEmpty(f.ParentId))
        result[f.Id].Parent = result[f.ParentId];

    var f2g = from t in msi.Elements("table")
              let name = t.Attribute("name")
              where name != null && name.Value == "FeatureExtensionData"
              from r in t.Elements("row")
              let elements = r.Elements("td").GetEnumerator()
              select new
              {
                Feature = result[GetElementValue(elements)],
                Guid = new Guid(GetElementValue(elements))
              };

    foreach (var f in f2g)
      f.Feature.Guid = f.Guid;

    return result;
  }

  public static string GetElementValue(IEnumerator<XElement> element)
  {
    if (element.MoveNext())
      return element.Current.Value;

    return null;
  }

  public static string LongName(this string path)
  {
    var i = path.IndexOf('|');
    if (i >= 0)
      return path.Substring(i + 1);

    return path;
  }
}
